#!/usr/bin/python
#
# Copyright 2005, Ferry Boender <f DOT boender AT electricmonk DOT nl>
# Improved by Josef Kyrian <josef.kyrian@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# USE xev to get key codes
 
"""
KeySimulator provides a way of inserting keys into the currently
focussed X window.
 
This is fairly hacky, seeying as I have no X11 programming experience
whatsoever.

Usage:
keysimulator.sendString("[shift+a]hoj lidi")
keysimulator.sendString("[shift+Up]")
keysimulator.sendString("[control+mod1+1]")
keysimulator.sendString("[shift+F1]")
"""
 
import Xlib.display
import Xlib.X
import Xlib.XK
import time
import re
import sys

# Characters that need the Shift modifier
# Stolen from crikey 
# Copyright 2003 by Akkana Peck, http://www.shallowsky.com/software/
# * Modified by Efraim Feinstein, 2004 
 
_shift_chars = [
	'~', '!', '@', '#', '$', '%', '^', '&', '*', '(', 
	')', '_', '+', '|', '{', '}', ':', '"', '<', '>', '?', 
]
 
class KeySimulator:
	"""
	KeySimulator class
	"""
 
	def __init__(self):
		"""
		Init
		"""
		self.display = Xlib.display.Display()
		self.screen = self.display.screen()
		self.root = self.screen.root
		self.window = self._getFocusedWindow()
 
		self._buildStringToKeysymMap()
	#enddef
 
	def sendString(self, string):
		"""
		Converts string to list of events and simulates real keystrokes
		"""
		self.window = self._getFocusedWindow()
 
		events = self._stringToKeyEvents(string)
		if events:
			self._sendEvents(self.window, events)
		#endif
	#enddef

	def _sendEvents(self, window, events):
		"""
		Simulate real event (keystroke)
		"""
		for event in events:
			window.send_event(event, propagate = True)
		#endfor
 
		self.display.sync()
	#enddef

	def _buildStringToKeysymMap(self):
		"""
		Xlib's string_to_keysym() doesn't seem to convert some chars to correct
		keysyms, but when doing it the other way (via keysym_to_string()) most
		characters do show up. So we build a map of all keysyms which we can
		reference.
		"""
 
		self.keysym_map = {}
 
		for keysym in range(0, 65535):
			string = Xlib.XK.keysym_to_string(keysym)
			if string:
				self.keysym_map[string] = keysym
			#endid
		#endfor
	#enddef

	def _getFocusedWindow(self):
		"""
		Returns currently focused window
		"""
		input_focus = self.display.get_input_focus()
 
		return(input_focus._data["focus"]);
	#enddef

	def _stringToKeyEvents(self, string):
		"""
		Uses string to create key events
		"""
		key_events = []
 
		parts = re.split("(\[.*?\])", string)
 
		# Get the keysym for the current character or special key in the string
		for part in parts:
 
			if part == '':
				# RegEx artifact. ;-)
				pass
			elif part[0] == '[':
				# Special command
				command = part[1:-1]
 
				mod_cmd = command.split('+')
				state = 0
				for p in mod_cmd:
					if p == "shift":
						state += Xlib.X.ShiftMask
					#endif
					if p == "control":
						state += Xlib.X.ControlMask
					#endif
					if p == "lock":
						state += Xlib.X.LockMask
					#endif
					if p == "mod1":
						state += Xlib.X.Mod1Mask
					#endif
					if p == "mod2":
						state += Xlib.X.Mod2Mask
					#endif
					if p == "mod3":
						state += Xlib.X.Mod3Mask
					#endif
					if p == "mod4":
						state += Xlib.X.Mod4Mask
					#endif
					if p == "mod5":
						state += Xlib.X.Mod5Mask
					else:
						cmd = p
					#endif
				#endfor
 
				keysym = Xlib.XK.string_to_keysym(cmd)
				if keysym > 0:
					keycode = self.display.keysym_to_keycode(keysym)
					key_events.extend(self._createKeyEvents(keycode, state))
				else:
					sys.stderr.write("Unknown keysym '%s'\n" % keysym)
				#endif
			else:
				# String of normal keys
				for c in part:
					if c in self.keysym_map:
						keysym = self.keysym_map[c]
						keycode = self.display.keysym_to_keycode(keysym)
						if c in _shift_chars:
							key_events.extend(self._createKeyEvents(keycode, Xlib.X.ShiftMask))
						else:
							key_events.extend(self._createKeyEvents(keycode))
						#endif
					#endif
				#endfor
			#endif
		#endfor
 
		return(key_events)
 	#enddef

	def _createKeyEvents(self, key_code, state = 0):
		"""
		Returns key event instance
		"""
		#key_code = 112
		#state = 0x10
		#print '%0x | %0x' % (key_code, state)
		evPress = Xlib.protocol.event.KeyPress(
			time = int(time.time()),
			root = self.root,
			window = self.window,
			same_screen = True,
			child = Xlib.X.NONE, 
			root_x = 0,
			root_y = 0,
			event_x = 0,
			event_y = 0,
			state = state, # X.h 'state' -> shift, etc
			detail = key_code
		)
		evRelease = Xlib.protocol.event.KeyRelease(
			time = int(time.time()),
			root = self.root,
			window = self.window,
			same_screen = True,
			child = Xlib.X.NONE, 
			root_x = 0,
			root_y = 0,
			event_x = 0,
			event_y = 0,
			state = state, # X.h 'state' -> shift, etc
			detail = key_code
		)
 
		return [evPress, evRelease]
	#enddef
#endclass


if __name__ == '__main__':
	time.sleep(2)
	keySimulator = KeySimulator()
	keySimulator.sendString(''.join(sys.argv[1:]))
#endif
